/*
 * Copyright (c) 2012 Carsten Munk <carsten.munk@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <EGL/egl.h>
#include <GLES2/gl2.h>
#include <assert.h>
#include <stdio.h>
#include <math.h>
#include <stddef.h>
#include <hardware/hardware.h>
#include <hardware/hwcomposer.h>
#include <malloc.h>
#include <sync/sync.h>
#include <pthread.h>

pthread_mutex_t count_mutex     = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  condition_var   = PTHREAD_COND_INITIALIZER;
int count = 0;

const char vertex_src [] =
"                                        \
   attribute vec4        position;       \
   varying mediump vec2  pos;            \
   uniform vec4          offset;         \
                                         \
   void main()                           \
   {                                     \
      gl_Position = position + offset;   \
      pos = position.xy;                 \
   }                                     \
";


const char fragment_src [] =
"                                                      \
   varying mediump vec2    pos;                        \
   uniform mediump float   phase;                      \
                                                       \
   void  main()                                        \
   {                                                   \
      gl_FragColor  =  vec4( 1., 0.9, 0.7, 1.0 ) *     \
        cos( 30.*sqrt(pos.x*pos.x + 1.5*pos.y*pos.y)   \
             + atan(pos.y,pos.x) - phase );            \
   }                                                   \
";

GLuint load_shader(const char *shader_source, GLenum type)
{
  GLuint  shader = glCreateShader(type);

  glShaderSource(shader, 1, &shader_source, NULL);
  glCompileShader(shader);

  return shader;
}


GLfloat norm_x    =  0.0;
GLfloat norm_y    =  0.0;
GLfloat offset_x  =  0.0;
GLfloat offset_y  =  0.0;
GLfloat p1_pos_x  =  0.0;
GLfloat p1_pos_y  =  0.0;

GLint phase_loc;
GLint offset_loc;
GLint position_loc;

const float vertexArray[] = {
  0.0,  1.0,  0.0,
  -1.,  0.0,  0.0,
  0.0, -1.0,  0.0,
  1.,  0.0,  0.0,
  0.0,  1.,  0.0
};


class HWComposer : public cb_context
{
  private:
    hwc_layer_1_t *fblayer;
    hwc_composer_device_1_t *hwcdevice;
    hwc_display_contents_1_t **mlist;
  protected:
    void present(HWComposerNativeWindowBuffer *buffer);

  public:

    HWComposer(unsigned int width, unsigned int height, unsigned int format, hwc_composer_device_1_t *device, hwc_display_contents_1_t **mList, hwc_layer_1_t *layer);
    void set(); 
};

HWComposer::HWComposer(unsigned int width, unsigned int height, unsigned int format, hwc_composer_device_1_t *device, hwc_display_contents_1_t **mList, hwc_layer_1_t *layer) : HWComposerNativeWindow(width, height, format)
{
  fblayer = layer;
  hwcdevice = device;
  mlist = mList;
}

void HWComposer::present(HWComposerNativeWindowBuffer *buffer)
{
  int oldretire = mlist[0]->retireFenceFd;
  mlist[0]->retireFenceFd = -1;
  fblayer->handle = buffer->handle;
  fblayer->acquireFenceFd = getFenceBufferFd(buffer);
  fblayer->releaseFenceFd = -1;
  int err = hwcdevice->prepare(hwcdevice, HWC_NUM_DISPLAY_TYPES, mlist);
  assert(err == 0);

  err = hwcdevice->set(hwcdevice, HWC_NUM_DISPLAY_TYPES, mlist);
  //assert(err == 0);
  setFenceBufferFd(buffer, fblayer->releaseFenceFd);

  if (oldretire != -1)
  {   
    sync_wait(oldretire, -1);
    close(oldretire);
  }
} 

hwc_procs_t procs;

void hook_invalidate(const struct hwc_procs* procs) {
    procs = procs;
    printf("invalidate\n");
}

void advance_cursor() {
  static int pos=0;
  char cursor[4]={'/','-','\\','|'};
  printf("%c\b", cursor[pos]);
  fflush(stdout);
  pos = (pos+1) % 4;
}

void hook_vsync(const struct hwc_procs* procs, int disp,
        int64_t timestamp) {
    procs = procs;
    disp = disp;
    timestamp = timestamp;

    pthread_mutex_lock( &count_mutex );
    count++;
    pthread_cond_signal( &condition_var );
    pthread_mutex_unlock( &count_mutex );

    //printf("vsync\n");
    advance_cursor();
}

void hook_hotplug(const struct hwc_procs* procs, int disp,
        int connected) {
    procs = procs;
    disp = disp;
    connected = connected;
    printf("hotplug\n");
}

int main(int argc, char **argv)
{
        machybris_init(argc, argv);

  EGLDisplay display;
  EGLConfig ecfg;
  EGLint num_config;
  EGLint attr[] = {       // some attributes to set up our egl-interface
    EGL_BUFFER_SIZE, 32,
    EGL_RENDERABLE_TYPE,
    EGL_OPENGL_ES2_BIT,
    EGL_NONE
  };
  EGLSurface surface;
  EGLint ctxattr[] = {
    EGL_CONTEXT_CLIENT_VERSION, 2,
    EGL_NONE
  };
  EGLContext context;

  EGLBoolean rv;
  int err;  

        hw_module_t const* module = NULL;
        err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
        assert(err == 0);
        framebuffer_device_t* fbDev = NULL;
        framebuffer_open(module, &fbDev);

  hw_module_t *hwcModule = 0;
  hwc_composer_device_1_t *hwcDevicePtr = 0;

  err = hw_get_module(HWC_HARDWARE_MODULE_ID, (const hw_module_t **) &hwcModule);
  assert(err == 0);

  err = hwc_open_1(hwcModule, &hwcDevicePtr);
  assert(err == 0);

        //framebuffer_close(fbDev);

        if(hwcDevicePtr->registerProcs) {
            printf("hwc already has procs! now overriding...\n");
            procs.invalidate = &hook_invalidate;
            procs.vsync = &hook_vsync;
            procs.hotplug = &hook_hotplug;
            hwcDevicePtr->registerProcs(hwcDevicePtr, &procs);
        }
        hwcDevicePtr->blank(hwcDevicePtr, 0, 0);
        hwcDevicePtr->eventControl(hwcDevicePtr, 0, HWC_EVENT_VSYNC, 1);

  uint32_t configs[5];
  size_t numConfigs = 5;

  err = hwcDevicePtr->getDisplayConfigs(hwcDevicePtr, 0, configs, &numConfigs);
  assert (err == 0);

  int32_t attr_values[2];
  uint32_t attributes[] = { HWC_DISPLAY_WIDTH, HWC_DISPLAY_HEIGHT, HWC_DISPLAY_NO_ATTRIBUTE }; 

  hwcDevicePtr->getDisplayAttributes(hwcDevicePtr, 0,
      configs[0], attributes, attr_values);

  printf("width: %i height: %i\n", attr_values[0], attr_values[1]);

  size_t size = sizeof(hwc_display_contents_1_t) + 2 * sizeof(hwc_layer_1_t);
  hwc_display_contents_1_t *list = (hwc_display_contents_1_t *) malloc(size);
  hwc_display_contents_1_t **mList = (hwc_display_contents_1_t **) malloc(HWC_NUM_DISPLAY_TYPES * sizeof(hwc_display_contents_1_t *));
  const hwc_rect_t r = { 0, 0, attr_values[0], attr_values[1] };

  int counter = 0;
  for (; counter < HWC_NUM_DISPLAY_TYPES; counter++)
    mList[counter] = list;

  hwc_layer_1_t *layer = &list->hwLayers[0];
  memset(layer, 0, sizeof(hwc_layer_1_t));
  layer->compositionType = HWC_FRAMEBUFFER;
  layer->hints = 0;
  layer->flags = 0;
  layer->handle = 0;
  layer->transform = 0;
  layer->blending = HWC_BLENDING_NONE;
  layer->sourceCrop = r;
  layer->displayFrame = r;
  layer->visibleRegionScreen.numRects = 1;
  layer->visibleRegionScreen.rects = &layer->displayFrame;
  layer->acquireFenceFd = -1;
  layer->releaseFenceFd = -1;
        layer->planeAlpha = 0xFF;
  layer = &list->hwLayers[1];
  memset(layer, 0, sizeof(hwc_layer_1_t));
  layer->compositionType = HWC_FRAMEBUFFER_TARGET;
  layer->hints = 0;
  layer->flags = 0;
  layer->handle = 0;
  layer->transform = 0;
  layer->blending = HWC_BLENDING_NONE;
  layer->sourceCrop = r;
  layer->displayFrame = r;
  layer->visibleRegionScreen.numRects = 1;
  layer->visibleRegionScreen.rects = &layer->displayFrame;
  layer->acquireFenceFd = -1;
  layer->releaseFenceFd = -1;
        layer->planeAlpha = 0xFF;

  list->retireFenceFd = -1;
  list->flags = HWC_GEOMETRY_CHANGED;
  list->numHwLayers = 2;


  HWComposer *win = new HWComposer(attr_values[0], attr_values[1], HAL_PIXEL_FORMAT_RGBA_8888, hwcDevicePtr, mList, &list->hwLayers[1]);

  display = eglGetDisplay(NULL);
  assert(eglGetError() == EGL_SUCCESS);
  assert(display != EGL_NO_DISPLAY);

  rv = eglInitialize(display, 0, 0);
  assert(eglGetError() == EGL_SUCCESS);
  assert(rv == EGL_TRUE);

  eglChooseConfig((EGLDisplay) display, attr, &ecfg, 1, &num_config);
  assert(eglGetError() == EGL_SUCCESS);
  assert(rv == EGL_TRUE);



  surface = eglCreateWindowSurface((EGLDisplay) display, ecfg, (EGLNativeWindowType) static_cast<ANativeWindow *> (win), NULL);
  assert(eglGetError() == EGL_SUCCESS);
  assert(surface != EGL_NO_SURFACE);

  context = eglCreateContext((EGLDisplay) display, ecfg, EGL_NO_CONTEXT, ctxattr);
  assert(eglGetError() == EGL_SUCCESS);
  assert(context != EGL_NO_CONTEXT);

  assert(eglMakeCurrent((EGLDisplay) display, surface, surface, context) == EGL_TRUE);
        eglSwapInterval((EGLDisplay)display, 1);

  const char *version = (const char *)glGetString(GL_VERSION);
  assert(version);
  printf("%s\n",version);

  GLuint vertexShader   = load_shader ( vertex_src , GL_VERTEX_SHADER  );     // load vertex shader
  GLuint fragmentShader = load_shader ( fragment_src , GL_FRAGMENT_SHADER );  // load fragment shader

  GLuint shaderProgram  = glCreateProgram ();                 // create program object
  glAttachShader ( shaderProgram, vertexShader );             // and attach both...
  glAttachShader ( shaderProgram, fragmentShader );           // ... shaders to it

  glLinkProgram ( shaderProgram );    // link the program
  glUseProgram  ( shaderProgram );    // and select it for usage

  //// now get the locations (kind of handle) of the shaders variables
  position_loc  = glGetAttribLocation  ( shaderProgram , "position" );
  phase_loc     = glGetUniformLocation ( shaderProgram , "phase"    );
  offset_loc    = glGetUniformLocation ( shaderProgram , "offset"   );
  if ( position_loc < 0  ||  phase_loc < 0  ||  offset_loc < 0 ) {
    return 1;
  }

  //glViewport ( 0 , 0 , 800, 600); // commented out so it uses the initial window dimensions
  glClearColor ( 1. , 1. , 1. , 1.);    // background color
  float phase = 0;
  int i, oldretire = -1, oldrelease = -1, oldrelease2 = -1;
  for (i=0; i<20*60; ++i) {
    glClear(GL_COLOR_BUFFER_BIT);
    glUniform1f ( phase_loc , phase );  // write the value of phase to the shaders phase
    phase  =  fmodf ( phase + 0.5f , 2.f * 3.141f );    // and update the local variable

    glUniform4f ( offset_loc  ,  offset_x , offset_y , 0.0 , 0.0 );

    glVertexAttribPointer ( position_loc, 3, GL_FLOAT, GL_FALSE, 0, vertexArray );
    glEnableVertexAttribArray ( position_loc );
    glDrawArrays ( GL_TRIANGLE_STRIP, 0, 5 );

                pthread_mutex_lock( &count_mutex );
                count=0;
                pthread_cond_wait( &condition_var, &count_mutex );
                pthread_mutex_unlock( &count_mutex );

    eglSwapBuffers ( (EGLDisplay) display, surface );  // get the rendered buffer to the screen
  }

  printf("stop\n");

#if 0
  (*egldestroycontext)((EGLDisplay) display, context);
  printf("destroyed context\n");

  (*egldestroysurface)((EGLDisplay) display, surface);
  printf("destroyed surface\n");
  (*eglterminate)((EGLDisplay) display);
  printf("terminated\n");
  android_dlclose(baz);
#endif
}

// vim:ts=4:sw=4:noexpandtab